Skip to content

feat: add t.openIsolatedSession() for multi-user e2e tests via CDP - #8492

Open
sam-yh wants to merge 5 commits into
DevExpress:masterfrom
sam-yh:upstream-pr
Open

sam-yh wants to merge 5 commits into
DevExpress:masterfrom
sam-yh:upstream-pr

Conversation

@sam-yh

@sam-yh sam-yh commented Mar 17, 2026 •

Copy link
Copy Markdown

Summary

Adds t.openIsolatedSession(). It creates a Chromium browser context over CDP (Target.createBrowserContext) with its own cookies, storage, cache and HTTP auth, opens a page target in it and resolves to an IsolatedTestController for that page. One test can then drive two users in one browser: doctor and patient in a video call, two editors of the same document, concurrent bookings.

Related: #5291 (different roles in different windows, closed as not planned).

API

import { Selector } from 'testcafe';

fixture('Multi-user').page('https://app.example/');

test('Two users', async t => {
    await t.useRole(doctor);

    const t2 = await t.openIsolatedSession();

    await t2.navigateTo('https://app.example/call/123');
    await t2.setHttpAuth('user', 'pass');
    await t2.typeText('#name', 'Patient').click('#join');

    await t2.expect(Selector('.participant').count).eql(2);
    await t.expect(Selector('.participant').count).eql(2);

    await t2.run(async () => {
        await joinCallPage.acceptConsent();
        await t.expect(Selector('#video').visible).ok();
    });
});

t2 accepts CSS strings and Selector objects (including chains: withText, withExactText, withAttribute, nth, find, parent, child, sibling, nextSibling, prevSibling, filterVisible, filterHidden). Calls chain like on t, including expect. Sessions are disposed when the test ends.

Methods on t2

Actions: click, rightClick, doubleClick, hover, drag, dragToElement, typeText, pressKey, selectText, scroll, scrollBy, scrollIntoView, dispatchEvent, setFilesToUpload, clearUpload, wait, navigateTo.
Other: expect, eval, useRole, getCookies, setCookies, deleteCookies, takeScreenshot, takeElementScreenshot, switchToIframe, switchToMainWindow, maximizeWindow, resizeWindow, setWindowBounds, setHttpAuth, setPageLoadTimeout, run.

Not on t2: openWindow and the window switching methods, getCurrentCDPSession, setNativeDialogHandler, getNativeDialogHistory, getBrowserConsoleMessages, addRequestHooks, removeRequestHooks, request, debug, setTestSpeed, skipJsErrors, report, selectTextAreaContent, selectEditableContent, resizeWindowToFitDevice, customActions, ctx/fixtureCtx/browser/test/fixture.

t2.run(fn)

t2.run executes fn with the isolated page as the target of everything that normally goes to the test page:

  • The global t resolves to t2 for the duration of the callback, so t.click(...), t.expect(...) and page objects written against t act on the isolated page.
  • Selector and ClientFunction calls made in the callback (property reads such as sel.innerText, sel.exists, assertions on selector properties, ClientFunction invocations) execute in the isolated page. Routing is a stack, so nested calls for another session and the main page after the callback are unaffected.
  • Calls in the callback chain like on t: await t.click(a).typeText(b, 'x').
  • Commands the callback enqueues but does not await are awaited before run resolves, so t2.run(() => { t.click('#a'); }) completes the click.
  • After the callback the global t is restored. Assertions on selector properties through t2.expect(...) work outside run as well; only bare Selector/ClientFunction calls need run to target the isolated page.
  • Methods that do not exist on t2 (see above) are not available on t inside the callback either.

Differences from t

  • Commands run over CDP in the isolated page without the TestCafe client scripts. Element resolution waits up to selectorTimeout and checks visibility, but the action itself is CDP input: typeText uses Input.insertText (no per-character key events) and appends unless replace or caretPos is given; pressKey sends key events for single keys and modifier combinations.
  • Assertions on selector properties retry until assertionTimeout with the same options as on t.
  • dispatchEvent builds the matching event class from eventName and its options; a relatedTarget option given as a selector is resolved to an element.
  • useRole applies the role's cookie snapshot; the role must have been initialized by t first, the isolated session cannot run the role's init function.
  • A native dialog (alert, confirm, prompt) in the isolated page is dismissed (confirm returns false, prompt returns null) and fails the t2 command that raised it; beforeunload is accepted. There is no dialog handler API on t2.
  • navigateTo throws when Chromium reports a navigation error instead of continuing on the error page.
  • setHttpAuth sets an Authorization header on every request from the isolated page, including cross-origin ones.
  • t2.eval and ClientFunction support serializable dependencies; ClientFunction(fn).with({ boundTestRun: t2 }) targets the isolated page.

Known limits

  • Chromium in native automation mode only. t.openIsolatedSession() in proxy mode throws E103.
  • No request hooks, RequestLogger or RequestMock for the isolated page: it is not proxied.
  • Cross-origin iframes are not supported by switchToIframe; same-origin iframes, nested ones and iframes picked by nth or attribute selectors are.
  • Function-form selectors (Selector(() => ...)) and .filter(fn) are not supported; use CSS, withText, withAttribute or the other chain methods.
  • Nested isolated sessions (t2.openIsolatedSession()) and openWindow on t2 are not available.
  • No TestCafe client scripts in the isolated page: no status bar, no cursor overlay, no t.getBrowserConsoleMessages equivalent.

Implementation

  • src/browser/provider/built-in/dedicated/chrome/cdp-client/index.ts: browser-level CDP connection (cached promise, reset on disconnect, closed with the browser), createIsolatedContext / disposeIsolatedContext.
  • src/browser/provider/built-in/dedicated/chrome/index.js: createIsolatedSession / disposeIsolatedSession / getBrowserLevelCDPClient.
  • src/native-automation/isolated-window.ts: page-level native automation for the isolated target without the request pipeline; src/native-automation/index.ts: the main window ignores targets from other browser contexts.
  • src/test-run/isolated-session.ts: command execution over CDP, selector and client function evaluation, assertions, iframe contexts, dialogs, cookies, screenshots; remote objects live in CDP object groups released when the last command in flight finishes.
  • src/api/test-controller/isolated.js: IsolatedTestController on the delegatedAPI pattern with its own command chain; t2.run.
  • src/test-run/index.ts, src/api/test-controller/index.js: openIsolatedSession task and session disposal in _done.
  • src/errors: E103 isolatedSessionRequiresNativeAutomationError.
  • ts-defs-src: IsolatedTestController and openIsolatedSession definitions.

Tests

  • test/functional/fixtures/isolated-sessions/: 88 tests (basic isolation 7, commands 19, actions 19, selector chaining 10, t2.run 11, iframes 9, cookies 5, screenshots 2, file upload 1, HTTP auth 1, roles 1, window management 3, native dialogs and navigation errors in the commands and actions fixtures). Gulp task test-functional-local-isolated-sessions.
  • test/server/isolated-context-test.js: 4 tests for context creation and disposal, disposal on attach failure, one shared browser connection, reconnect after disconnect.
  • test/server/isolated-session-test.js: 5 tests for remote-object release (command object group released after success, failure and overlapping commands; iframe handles kept until switchToMainWindow, released on navigation).
  • TypeScript definitions: test/server/data/test-suites/typescript-defs/isolated-sessions.ts and the TestController definitions check in compiler-test.js.
  • test/server/test-run-error-formatting-test.js: E103.

Verified locally with Chrome 154 headless, native automation: functional 88/88, server tests above, gulp lint clean.

Notes

The feature is in daily use in our multi-user e2e suite; the code was reviewed and reworked (element resolution, auto-wait, iframes, dialogs, leaks, t2.run routing) before this update. Happy to split the PR (CDP context creation first, controller second) or change the API shape if that helps review.

@testcafe-need-response-bot testcafe-need-response-bot Bot added the STATE: Need response An issue that requires a response or attention from the team. label Mar 17, 2026
@aleks-pro aleks-pro added the STATE: PR Review Pending A note that this PR will be reviewed. label Mar 23, 2026
@github-actions

Copy link
Copy Markdown

Thank you for your contribution to TestCafe. When a member of the TestCafe team becomes available, they will review this PR.

@github-actions github-actions Bot removed STATE: Need response An issue that requires a response or attention from the team. STATE: PR Review Pending A note that this PR will be reviewed. labels Mar 23, 2026
Add a browser-level CDP client to the Chrome BrowserClient and use it to
create and dispose browser contexts (Target.createBrowserContext) with one
page target each. A NativeAutomationIsolatedWindow attaches to such a
target with the page-level CDP setup only, without the request pipeline
and proxy hooks the main window uses.

The main window's targetCreated handler now ignores targets from other
browser contexts, so an isolated tab does not get treated as a child
window of the test page.

The browser-level connection is cached as a promise and forgotten on
disconnect and on close; a failed target creation disposes the context it
was created in so nothing leaks.
t.openIsolatedSession() opens a second browser session in an isolated CDP
browser context (own cookies, storage, cache and HTTP auth) and resolves to
an IsolatedTestController. Multi-user scenarios such as a video call
between two accounts can run inside one test in one browser instead of
two concurrent tests or two browsers.

The controller exposes the TestController action, assertion, cookie,
screenshot, window and role API and executes commands against the isolated
tab directly over CDP (Input, Runtime, Page, Network, Storage). Selectors
and client functions are compiled with the regular builders and evaluated
in the isolated page. t2.run(fn) executes a callback in which Selector,
ClientFunction and t.expect resolve against the isolated tab, so page
objects written for t work unchanged.

Isolated sessions are disposed when the test finishes, before the
before-done hooks. Opening one without native automation throws the coded
error E103.
Add the isolated-sessions functional suite (own gulp task, excluded from
the screenshot glob) covering isolation of cookies and storage, actions,
selectors and assertions, t2.run, iframes, roles, HTTP auth, file upload,
screenshots and window management. Add a server test for context
creation and disposal in the CDP client, TypeScript definition checks for
the new API, the E103 formatting test and the openIsolatedSession
exception in the TestController events test, since opening a session is a
task and not a logged command.
…caretPos in contenteditable

dispatchEvent forwarded relatedTarget inside the plain options object, so
an element passed as relatedTarget never reached the event as a node; it
is now resolved like the target and passed by object id. typeText with
caretPos placed the caret only in input and textarea elements; in a
contenteditable element the caret now moves to the requested offset.
…shes

Every element lookup created a Runtime remote object without an object
group and nothing released it, so handles lived until the isolated tab
closed and elements the page had removed stayed in memory; thrown
exceptions were retained too. Lookups, the scroll document and
evaluateExpression now use the isolated-command group, released with
Runtime.releaseObjectGroup when the last t2 command in flight finishes.
The iframe element entered with switchToIframe is copied into the
isolated-frames group and released on switchToMainWindow or navigateTo.

This branch is waiting to be deployed

1 waiting deployment
authentication — 514c7d58 Waiting Sep 28, 2026 by sam-yh via authenticate #5048
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants